use std::future::Future;

use ::oauth2::{http::{HeaderMap, Method, StatusCode}, AccessToken, HttpRequest, HttpResponse};
use serde::Deserialize;
use store::UserData;
use tracing::trace;

use super::OAuth2;

use crate::*;

pub struct TokenIntrospectionRequest<'r> {
    client: &'r OAuth2, 
    access_token: &'r AccessToken
}

impl<'r> TokenIntrospectionRequest<'r> {
    pub fn new(client: &'r OAuth2, access_token: &'r AccessToken) -> Self {        
        Self { client, access_token }
    }

    fn prepare_request(self) -> Result<HttpRequest> { 
        let mut headers = HeaderMap::new();
        headers.append("authorization", format!("Bearer {}", self.access_token.secret()).try_into()?);
        let url = self.client.client.introspection_url().unwrap().url().clone();
        let method = Method::GET;
        let body = vec![];
        
        Ok(HttpRequest { url, method, headers, body }) 
    }

    pub async fn request_async<C, F, RE>(
        self,
        http_client: C,
    ) -> Result<UserData>
    where
        C: FnOnce(HttpRequest) -> F,
        F: Future<Output = Result<HttpResponse, RE>>,
        RE: std::error::Error + 'static,
    {
        let http_request = self.prepare_request()?;
        let http_response = http_client(http_request)
            .await
            .map_err(|e| AuthError::RequestTokenError(Box::new(e)))?;
        trace!(event="introspection_response", 
            code=%http_response.status_code, 
            text=String::from_utf8_lossy(&http_response.body).into_owned()
        );
        if http_response.status_code != StatusCode::OK {
            let error_text = String::from_utf8_lossy(&http_response.body).into_owned();
            return Err(AuthError::Introspection(http_response.status_code, error_text))
        }
        let resp: JsonTokenIntrospectionResponse = serde_json::from_slice(&http_response.body)?;

        let phone = resp.default_phone.map(|p| p.number);
        let name = resp.display_name;
        let email = resp.default_email;
        Ok(UserData { phone, name, email })
        
    }    
    
}


/*
{
   "first_name": "Иван",
   "last_name": "Иванов",
   "display_name": "ivan",
   "emails": [
      "test@yandex.ru",
      "other-test@yandex.ru"
   ],
   "default_email": "test@yandex.ru",
   "default_phone": {
      "id": 12345678,
      "number": "+79037659418"
   },
   "real_name": "Иван Иванов",
   "is_avatar_empty": false,
   "birthday": "1987-03-12",
   "default_avatar_id": "131652443",
   "login": "ivan",
   "old_social_login": "uid-mmzxrnry",
   "sex": "male",
   "id": "1000034426",
   "client_id": "4760187d81bc4b7799476b42b5103713",
   "psuid": "1.AAceCw.tbHgw5DtJ9_zeqPrk-Ba2w.qPWSRC5v2t2IaksPJgnge"
}
*/

#[allow(unused)]
#[derive(Deserialize, Debug)]
struct Phone {
    id: i64,
    number: String,
}

#[allow(unused)]
#[derive(Deserialize, Debug)]
struct JsonTokenIntrospectionResponse {
    first_name: Option<String>,
    last_name: Option<String>,
    display_name: Option<String>,
    emails: Vec<String>,
    default_email: Option<String>,
    default_phone: Option<Phone>,
    real_name: Option<String>,
    default_avatar_id: Option<String>,
    login: Option<String>,
    old_social_login: Option<String>,
    sex: Option<String>,    
    id: Option<String>,
    client_id: Option<String>,
    psuid: Option<String>,
}

/*
{
   "iat": 1620915565,
   "jti": "384b6169-b3f6-11eb-a7cd-0c42a10aa38c",
   "exp": 1652451414,
   "iss": "login.yandex.ru",
   "uid": 1000034426,
   "login": "ivan",
   "avatar_id": "131652443",
   "email": "test@yandex.ru",
   "number": "+79037659418",
   "display_name": "ivan",
   "name": "Иван Иванов",
   "gender": "male",
   "psuid": "1.AAceCw.tbHgw5DtJ9_zeqPrk-Ba2w.qPWSRC5v2t2IaksPJgnge"
}
    */

#[allow(unused)]
#[derive(Deserialize, Debug)]
struct JwtTokenIntrospectionResponse {
    iat: i64,
    jti: String,
    exp: Option<i64>,
    iss: Option<String>,
    uid: i64,
    login: Option<String>,
    avatar_id: Option<String>,
    email: Option<String>,
    number: Option<String>,
    display_name: Option<String>,
    name: Option<String>,
    gender: Option<String>,
    psuid: Option<String>,
}